package uebung12.orienteering;

import java.util.Arrays;

import uebung12.orienteering.exceptions.*;

public class Orienteering {

	public Orienteering(char[][] area) throws InvalidAreaException {
		setArea(area);
	}

	private char[][] area;

	public char[][] getArea() {
		return area;
	}

	public void setArea(char[][] area) throws InvalidAreaException {
		this.area = area;
		if (!this.hasTwoWaypoints())
			throw new NotEnoughWaypointsException(
					"Die Karte besitzt weniger als 2 Wegpunkte, weshalb" +
					" ein Orientierungslauf nicht Simuliert werden kann.");
		if (!this.hasTerrainObstacle())
			throw new NoObstacleException("Die Karte muss mindestens ein Hindernis besitzen.");
		if (!this.hasPlainsElement())
			throw new NoPlainsException("Die Karte besitzt keine begehbaren Wege.");
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (char[] ch : area) {
			for (char c : ch) {
				sb.append(c);
			}
			sb.append('\n');
		}
		return sb.toString();
	}

	// help method to validate map.
	private int countElements(char element) {
		int count = 0;
		for (char[] ch : area) {
			for (char c : ch) {
				if (c == element)
					count++;
			}
		}
		return count;
	}

	private boolean hasTwoWaypoints() {
		if (countElements('#') > 0)
			return true;
		else
			return false;
	}

	private boolean hasPlainsElement() {
		if (countElements('0') > 0)
			return true;
		else
			return false;
	}

	private boolean hasTerrainObstacle() {
		if ((countElements('^') + countElements('~')) > 0)
			return true;
		else
			return false;
	}

	public void reachable(int x, int y) throws InvalidStartpointException {

		// check for valid startfield
		if (x < 0 || x > area.length || y < 0 || y > area[1].length)
			throw new InvalidCoordinatesException(
					"Die Koordinaten liegen außerhalb der Karte.");
		if (area[x][y] != '#')
			throw new NotAWaypointException(
					"Sie muessen als Startpunkt einen Wegpunkt angeben.");

		// start recursion of floodfill algorithm.
		flood(x, y);

	}

	private void flood(int x, int y) {
		// if x y still in area and field flat and dry, continue.
		try {
			switch (area[x][y]) {
			case '0':
				area[x][y] = '=';
				break;
			case '#':
				// area[x][y] = '#';
				break;
			default:
				return;
			}
		} catch (IndexOutOfBoundsException e) {
			// current field no longer in area. break recursion
			return;
		}
		// System.out.println(toString());
		flood(x + 1, y);
		flood(x - 1, y);
		flood(x, y + 1);
		flood(x, y - 1);
		return;

	}

	public int[][] analyzeCosts(int x, int y) throws InvalidStartpointException {

		// create int array with dimensions of area.
		costArea = new int[area.length][area[1].length];
		for (int i = 0; i < costArea.length; i++)
			Arrays.fill(costArea[i], Integer.MAX_VALUE);

		// analyze reachablity
		this.reachable(x, y);

		// check for valid startfield
		if (x < 0 || x > area.length || y < 0 || y > area[1].length)
			throw new InvalidCoordinatesException(
					"Die Koordinaten liegen außerhalb der Karte.");
		if (area[x][y] != '#')
			throw new NotAWaypointException(
					"Sie muessen als Startpunkt einen Wegpunkt angeben.");

		// start recursion of floodfill algorithm.
		floodCost(x, y, 0);

		// replace Integer.MAX_VALUE with -1.
		for (int i = 0; i < costArea.length; i++) {
			for (int j = 0; j < costArea[1].length; j++) {
				if (costArea[j][i] == Integer.MAX_VALUE)
					costArea[j][i] = -1;
			}
		}

		return costArea;

	}

	private int[][] costArea;

	private void floodCost(int x, int y, int steps) {

		// if x y still in area and field flat and dry, continue.
		try {
			if (area[x][y] == '#' || area[x][y] == '=') {
				if (costArea[x][y] > steps)
					costArea[x][y] = steps;
				else
					return;
			} else
				return;

		} catch (IndexOutOfBoundsException e) {
			// current field no longer in area. break recursion
			return;
		}

		floodCost(x + 1, y, steps + 1);
		floodCost(x - 1, y, steps + 1);
		floodCost(x, y + 1, steps + 1);
		floodCost(x, y - 1, steps + 1);
		return;

	}
}
